// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const TRACE_ID_PREFIX = 'trace_';
const DELAY_CONNECTOR = ' - ';
// 阶段性耗时计算将会使用该数组顺序，故该值需保持时序。
const ACTION_TAGS = ['receive', 'parse', 'decode', 'play'];
const ACTION_STATUS_TAGS = {
    start: 'start',
    end: 'end',
    message: 'message'
};
const TRACE_ID_MAX_NUM = 10000;

class DelayAnalysis {
    constructor(tags = [], size = 10000) {
        this.tags = tags;
        this.size = size;
        this.recordCache = [];
        this.traceIds = {};
    }

    allocTraceId() {
        return TRACE_ID_PREFIX + Date.now();
    }

    // 缓存traceId。存取间按顺序处理数据包且不便于带入traceId 参数的情况下，可根据先进先出原则存取traceId
    cacheTraceId(type, traceId) {
        if (!this.traceIds[type]) {
            this.traceIds[type] = [];
        } else if (this.traceIds[type].length >= TRACE_ID_MAX_NUM) {
            this.traceIds[type].shift();
        }
        
        this.traceIds[type].push(traceId || '');
    }

    shiftTraceId(type) {
        if (!this.traceIds[type]) {
            return 0;
        }

        return this.traceIds[type].shift();
    }

    // 收集打点数据。tags至少包含ACTION_TAGS 和ACTION_STATUS_TAGS 的成员
    record(tags, message, timestamp) {
        /*global __IS_DEBUG__*/
        if (!__IS_DEBUG__) {
            return;
        }

        const now = timestamp || (performance && performance.now());
        this.cache(tags, message, now);
    }

    cache(tags, message, timestamp) {
        if (this.recordCache.length >= this.size) {
            this.recordCache.shift();
        }

        this.recordCache.push({
            timestamp,
            tags,
            message
        });
    }

    /**
     * 原始打点数据按照traceId归类处理
     * @returns {object} traces: {traceId1: {id: traceId1, tag1: timestamp1, tag2: timestamp2...}, traceId2...};
     * sortTags: tag列表，打印traces信息是可以作为表头
     **/
    trace() {
        let traces = {};
        let sortTags = [];
        this.recordCache.forEach(item => {
            let tags = item.tags || [];
            let traceId = tags.find && tags.find(tag => tag.startsWith(TRACE_ID_PREFIX));
            let isMsg = tags.includes && tags.includes('message');
            let name = tags.filter && tags.filter(tag => !tag.startsWith('trace_')).join('|');
            if (traceId) {
                !traces[traceId] && (traces[traceId] = {id: traceId});
                traces[traceId][name] = isMsg ? item.message : item.timestamp;
                if (!sortTags.includes(name)) {
                    sortTags.push(name);
                }
            }
        });

        return {
            traces,
            sortTags
        };
    }

    /**
     * 原始数据基础上计算出traceId各阶段耗时
     * @returns {object} traces: 增加了tag间耗时信息的traces；sortTags: tag列表，打印traces信息是可以作为表头
     **/
    analysis() {
        let data = this.trace();
        let traces = data.traces;
        let sortTags = ['id'];
        let hasSortTags = false;
        Object.keys(traces).forEach(id => {
            const trace = traces[id];
            const tags = Object.keys(trace);
            let preTrace = null;
            // 按照ACTION_TAGS的顺序计算阶段性耗时
            ACTION_TAGS.forEach(action => {
                const startTag = tags.find(tag => tag.indexOf(action) > -1 && tag.indexOf(ACTION_STATUS_TAGS.start) > -1);
                const endTag = tags.find(tag => tag.indexOf(action) > -1 && tag.indexOf(ACTION_STATUS_TAGS.end) > -1);
                const msgTags = tags.filter(tag => tag.indexOf(action) > -1 && tag.indexOf(ACTION_STATUS_TAGS.message) > -1);

                if (preTrace && (startTag || endTag)) {
                    const firstTag = startTag || endTag;
                    const newTag = firstTag + DELAY_CONNECTOR + preTrace.tag;
                    !hasSortTags && sortTags.push(newTag);
                    trace[newTag] = (trace[firstTag] - preTrace.timestamp).toFixed(3);
                }

                !hasSortTags && startTag && (sortTags.push(startTag));

                if (startTag && endTag) {
                    !hasSortTags && sortTags.push(endTag + DELAY_CONNECTOR + startTag);
                    trace[endTag + DELAY_CONNECTOR + startTag] = (trace[endTag] - trace[startTag]).toFixed(3);
                }

                const lastTag = endTag || startTag;
                lastTag && (preTrace = {
                    tag: lastTag,
                    timestamp: trace[lastTag]
                });

                if (!hasSortTags) {
                    endTag && (sortTags.push(endTag));
                    msgTags.forEach(item => sortTags.push(item));
                }
            });

            preTrace = null;
            // 认为每个traceId所有的tag名称是一样
            hasSortTags = true;
        });

        return {
            traces,
            sortTags
        };
    }

    exportFile(needAnalysis) {
        let data = null;
        if (needAnalysis) {
            data = this.analysis();
        } else {
            data = this.trace();
        }

        const traces = data.traces;
        const sortTags = data.sortTags;
        let csvContent = sortTags.join(',') + '\n';
        Object.keys(traces).forEach(id => {
            let trace = traces[id];
            sortTags.forEach(tag => {
                csvContent += (trace[tag] + ',');
            });
            csvContent += '\n';
        });

        let cvsData = new Blob([csvContent], {type: 'application/octet-stream'});
        let anchor = document.createElement('a');
        anchor.href = window.URL.createObjectURL(cvsData);
        anchor.download = `delay-analysis-${Date.now()}.csv`;
        document.body.appendChild(anchor);
        anchor.click();
        document.body.removeChild(anchor);
        window.URL.revokeObjectURL(cvsData);
    }
}

let delayAnalysis = new DelayAnalysis(ACTION_TAGS, 100000);
export default delayAnalysis;
